#pragma once

#include <vex/iterable/contract/range.h>
#include <vex/functional/contract/delegate.h>
#include <vex/functional/result.h>
#include <vex/functional/replace_result.h>

namespace vex { namespace functional { 

    struct removal_mode {
        enum domain {
            last,
            all
        };
    };

    namespace contract {

        /// <summary>
        /// A multicast delegate represents a collection of delegates (invocation list) with signatures
        /// <paramref name="TSignature"/>. If invoked, the elements of the invocation list are invoked 
        /// sequentially in the order of addition. By default, the return value of invoke is the same as 
        /// the return value infered from TSignature and corresponds to value returned by last delegate 
        /// in the invocation list.
        /// It is possible to define a different return value type. The default implementation supports 
        /// so called "reducers" which can be used to transform the return values of each delegate 
        /// in the invocation list to a new value of possibly different type. 
        /// The contract prescribes immutability which any conforming implementation must preserve. 
        /// </summary>
        template<class TSignature, class TRet = typename result<TSignature>::type> 
        struct VEX_ABSTRACT multicast_delegate 
            : public delegate< typename replace_result<TSignature, TRet>::type > 
        {
            typedef TSignature signature_type;
            typedef TRet result_type;

            /// <summary>
            /// Accepted delegate type
            /// </summary>
            typedef delegate<TSignature> delegate_type;

            /// <summary>
            /// Type of the invocation list
            /// </summary>
            typedef iterable::contract::range<
                iterable::one_pass, 
                delegate_type*
            > invocation_list_type;

            /// <summary>
            /// Append delegate <paramref name="p_delegate"/> to invocation list of this multicast delegate.
            /// Implementations create a new instance with <paramref name="p_delegate"/> added to invocation list. 
            /// If exception is thrown during combination process or some other error occurs nullptr
            /// shall be returned and this instance will stay unmodified.
            /// </summary>
            /// <param name="p_delegate">
            /// Delegate to add to current invocation list
            /// </param>
            /// <returns>
            /// A new delegate with <paramref name="p_delegate"/> appended to previous invocation list. 
            /// Returns nullptr if <paramref name="p_delegate"/> is nullptr or if any error occurs 
            /// during the combination process
            /// </returns>
            virtual multicast_delegate* combine(
                delegate<TSignature>* p_delegate/*, 
                delegate<multicast_delegate* (invocation_list_type*)> const* p_factory = nullptr */) const = 0;

            /// <summary>
            /// Concatenate invocation list of this multicast delegate with <paramref name="p_delegate_list"/>.
            /// Implementations create a new instance with <paramref name="p_delegate_list"/> concatenated with 
            /// current invocation list. If exception is thrown during combination process or some 
            /// other error occurs nullptr shall be returned and this instance will stay unmodified.
            /// </summary>
            /// <param name="p_delegate_list">
            /// A range of delegates which is combined with current invocation list.
            /// </param>
            /// <returns>
            /// A new delegate with <paramref name="p_delegate_list"/> concatenated with previous invocation list. 
            /// Returns nullptr if <paramref name="p_delegate_list"/> is nullptr or empty or if 
            /// any error occurs during the combination process
            /// </returns>
            virtual multicast_delegate* combine(invocation_list_type* p_delegate_list) const = 0;

            /// <summary>
            /// Implementations create a new instance with <paramref name="p_delegate"/> removed 
            /// from current invocation list. If exception is thrown during combination process 
            /// or some other error occurs nullptr shall be returned and this instance will stay 
            /// unmodified. If current invocation list does not contain <paramref name="p_delegate"/>, 
            /// nullptr shall be returned and this instance will stay unmodified. 
            /// Possible values for <paramref name="p_removal_mode"/> 
            /// are: <c>removal_mode::last</c> removes last occurrence of <paramref name="p_delegate"/> from
            /// invocation list and <c>removal_mode::all</c> which removes all occurrences 
            /// of <paramref name="p_delegate"/> from invocation list
            /// </summary>
            ///
            /// <param name="p_delegate">Delegate to remove.</param>
            /// <param name="p_removal_mode">Remove either last or all occurences of delegate.</param>
            virtual multicast_delegate* remove(
                delegate<TSignature>* p_delegate, 
                removal_mode::domain p_removal_mode = removal_mode::last) const = 0;

            /// <summary>
            /// Implementations create a new instance with either all or last contiguous occurrences 
            /// of delegates in <paramref name="p_inv_list"/> removed from current invocation list. 
            /// If exception is thrown during combination process or some other error occurs 
            /// <c>nullptr</c> shall be returned and this instance will stay unmodified. If current 
            /// invocation list does not contain any occurrence of <paramref name="p_inv_list"/>, 
            /// <c>nullptr</c> shall be returned and this instance will stay unmodified. 
            /// Possible values for <paramref name="p_removal_mode"/> 
            /// are: <c>removal_mode::last</c> removes last contiguous occurrence of <paramref name="p_"/> from
            /// invocation list and <c>removal_mode::all</c> which removes all occurrences 
            /// of <paramref name="p_delegate"/> from invocation list
            /// </summary>
            ///
            /// <param name="p_inv_list">Invocation list to remove.</param>
            /// <param name="p_removal_mode">Remove either last or all occurences of invocation list.</param>
            virtual multicast_delegate* remove(
                invocation_list_type* p_inv_list, 
                removal_mode::domain p_removal_mode = removal_mode::last) const = 0;

            /// <summary>
            /// Implementations shall return the copy of the current snapshot of the invocation list.
            /// </summary>
            virtual invocation_list_type* get_invocation_list() const = 0;

            VEX_PROTECT_CONTRACT(multicast_delegate);
        };
    }
}}